以下三個函數功能相同,都是把 base64 轉換為 ArrayBuffer
測試看看哪個效能比較好
/**
* 把 base64 轉換為 ArrayBuffer
*
* @param {string} b64 要轉成 ArrayBuffer 的 base64
* @returns {ArrayBuffer}
*/
function f0(b64) {
return (Uint8Array.from(window.atob(b64), x => x.codePointAt(0))).buffer;
}
/**
* 功能同 f0
* 嘗試以下方向改善效能:
* 1. 預先計算 ArrayBuffer 大小
* 2. 用 String.prototype.codePointAt 來減少字串的處理
*/
function f1(b64) {
let str = window.atob(b64);
let arr = new Uint8Array(str.length);
for(let i=arr.length;--i>=0;) {
arr[i] = str.codePointAt(i);
}
return arr.buffer;
}
/**
* 功能同 f1
* 不使用內建的 window.atob 而是自己處理轉換
*/
function f2(b64) {
let m = (() => {
let arr = [];
for (let i = 25; i >= 0; --i) {
arr[97 + i] = 26 + i;
arr[65 + i] = i;
}
for (let i = 9; i >= 0; --i) {
arr[48 + i] = 52 + i;
}
arr[43] = 62;
arr[47] = 63;
arr[61] = 0;
return arr;
})();
let n = b64.length, t, k = -1, i;
if (n < 4) {
return null;
}
let emptyBtyes = b64.codePointAt(n - 2) === 61 ? 2 :
(b64.codePointAt(n - 1) === 61 ? 1 : 0);
let arr = new Uint8Array((n >>> 2) * 3 - emptyBtyes);
n -= 4;
for (i = 3; i < n; i += 4) {
t = m[b64.codePointAt(i - 3)] << 18 |
m[b64.codePointAt(i - 2)] << 12 |
m[b64.codePointAt(i - 1)] << 6 |
m[b64.codePointAt(i)];
arr[++k] = (t >>> 16 & 255);
arr[++k] = (t >>> 8 & 255);
arr[++k] = (t & 255);
}
n += 4;
for (; i < n; i += 4) {
t = m[b64.codePointAt(i - 3)] << 18 |
m[b64.codePointAt(i - 2)] << 12 |
m[b64.codePointAt(i - 1)] << 6 |
m[b64.codePointAt(i)];
let len = arr.byteLength;
if (++k < len) {
arr[k] = t >>> 16 & 255;
}
if (++k < len) {
arr[k] = t >>> 8 & 255;
}
if (++k < len) {
arr[k] = t & 255;
}
}
return arr.buffer;
}
測試的程式碼如下
/**
* 比較兩個 ArrayBuffer 內容是否相同
*
* @param {ArrayBuffer} buf1
* @param {ArrayBuffer} buf2
* @returns {bool}
*/
function compareBuffer(buf1, buf2) {
let arr1 = new Uint8Array(buf1);
let arr2 = new Uint8Array(buf2);
if (arr1.length !== arr2.length) {
return false;
}
for (let i = arr1.length - 1; i >= 0; --i) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
/**
* 產生指定大小的 ArrayBuffer
* 其內容用亂數填充
*
* @param {int} len 要幾 bytes
* @returns {ArrayBuffer}
*/
function genRandArray(len) {
let arr = new Uint8Array(len);
for (let i = 0; i < len; ++i) {
arr[i] = Math.random() * 256 | 0;
}
return arr.buffer;
}
/**
* 把 ArrayBuffer 轉為 Base64
* (這邊先不考慮效能)
*
* @param {ArrayBuffer} buffer
* @returns {String} Base64
*/
function getBase64(buffer) {
let arr = new Uint8Array(buffer);
let len = arr.byteLength;
let str = '';
for (let i = 0; i < len; ++i) {
str += String.fromCodePoint(arr[i]);
}
return window.btoa(str);
}
//30 ~ 40M 大小
let origBuffer = genRandArray(30000000 + Math.random() * 10000000 | 0);
let origBase64 = getBase64(origBuffer);
let t0 = Date.now();
let buf0 = f0(origBase64);
let t1 = Date.now();
let buf1 = f1(origBase64);
let t2 = Date.now();
let buf2 = f2(origBase64);
let t3 = Date.now();
console.log(`f0: ${t1 - t0} ms ... ${compareBuffer(origBuffer, buf0)?'pass':'error'}`);
console.log(`f1: ${t2 - t1} ms ... ${compareBuffer(origBuffer, buf1)?'pass':'error'}`);
console.log(`f2: ${t3 - t2} ms ... ${compareBuffer(origBuffer, buf2)?'pass':'error'}`);
以下是某一次測試的結果(參考)
瀏覽器 | f0 | f1 | f2 |
---|---|---|---|
firefox | 1888 ms | 479 ms | 613 ms |
chrome | 3700 ms | 583 ms | 222 ms |